package wire

import (
	"github.com/lucas-clemente/quic-go/internal/protocol"
	"io"
	"bytes"
	"github.com/lucas-clemente/quic-go/internal/utils"
	"errors"
	"github.com/lucas-clemente/quic-go/qerr"
)

// TODO: handle FEC symbols !

// A FECFrame of FEC-QUIC
type FECFrame struct {
	FECPayloadID					protocol.FECPayloadID
	Convolutional					bool
	FECGroup        	  	protocol.FECGroup
	FinBit          			bool                                 // true if this is the end of a FECFrame payload
	DataLength      			protocol.FecFrameLength              // length of the part of payload contained in this frame (should hold on 31 bits to put the FIN bit in it ?)
	RepairSymbolNumber  	byte																 // number of the FEC symbol transiting in this FEC Frame for this particular FEC Group
	Offset          			protocol.FecFrameOffset              // offset number of a FECFrame (for big packets, FEC payloads could span across multiple FEC frames)
	NumberOfPackets 			byte																 // represents the number of packets protected by the FEC payload of this frame
	NumberOfRepairSymbols	byte																 // represents the total number of repair symbols for this FEC Group
	Data             			[]byte                               // payload part of this frame
}

// TODO: indicate in the partial reliability design the presence of the NumberOfRepairSymbols field
// TODO: indicate in the partial reliability design the fact that the Offset is relative to the NumberOfRepairSymbols

var _ Frame = &FECFrame{}

var (
	// We received a FEC_FRAME with a data length >= 2^15
	FECFrameTooHighDataLength = errors.New("FECFrame: DataLength should not be >= 2^15")
	FECFrameTooMuchData = errors.New("FECFrame: should not contain >= 2^15 bytes of data")
	FECFrameTooHighFECGroup   = errors.New("FECFrame: FECGroup should not be >= 2^48")
)

// ParseStreamFrame reads a stream frame. The type byte must not have been read yet.
func ParseFECFrame(r *bytes.Reader, version protocol.VersionNumber) (*FECFrame, error) {
	frame := &FECFrame{}
	_, err := r.ReadByte()
	if err != nil {
		return nil, err
	}

	finBitConvolutionalAndFrameLength, err := utils.BigEndian.ReadUint16(r)
	if err != nil {
		return nil, err
	}
	frame.FinBit = finBitConvolutionalAndFrameLength & 1 == 1
	frame.Convolutional = finBitConvolutionalAndFrameLength & 2 != 0
	frame.DataLength = protocol.FecFrameLength(finBitConvolutionalAndFrameLength >> 2)

	var fecGroup uint64
	if !frame.Convolutional {
		if !version.UsesIETFFrameFormat() {
			fecGroup, err = utils.BigEndian.ReadUintN(r, uint8(6))
		} else {
			fecGroup, err = utils.ReadVarInt(r)
		}
		if err != nil {
			return nil, err
		}
		frame.FECGroup = protocol.FECGroup(fecGroup)
	} else {
		fpid, err := utils.ReadVarInt(r)
		if err != nil {
			return nil, err
		}
		frame.FECPayloadID = protocol.FECPayloadID(fpid)
	}


	offset, err := r.ReadByte()
	if err != nil {
		return nil, err
	}
	frame.Offset = protocol.FecFrameOffset(offset)

	frame.RepairSymbolNumber, err = r.ReadByte()
	if err != nil {
		return nil, err
	}
	// if offset is 0, the first byte of the frame is the number of protected packets and the second byte is the number of repair symbols
	if frame.Offset == 0 {
		// read the number of protected packets
		// and the number of repair symbols for the FEC Group
		frame.NumberOfPackets, err = r.ReadByte()
		if err != nil {
			return nil, err
		}
		frame.NumberOfRepairSymbols, err = r.ReadByte()
		if err != nil {
			return nil, err
		}
	}

	if frame.DataLength != 0 {
		frame.Data = make([]byte, frame.DataLength)
		if  _, err := io.ReadFull(r, frame.Data); err != nil {
			return nil, err
		}
	}

	if len(frame.Data) == 0 {
		return nil, qerr.EmptyFECFrame
	}
	return frame, nil
}

//Writes a FECFrame
func (f FECFrame) Write(b *bytes.Buffer, version protocol.VersionNumber) error {
	if !version.UsesIETFFrameFormat() {
		b.WriteByte(0x0a)
	} else {
		b.WriteByte(0x0f)
	}
	if protocol.ByteCount(f.FECGroup) >= protocol.MaxByteCount {
		return FECFrameTooHighFECGroup
	}


	var lengthFinBitAndConvolutional uint16 = 0
	if f.FinBit {
		lengthFinBitAndConvolutional = 1
	}
	if f.Convolutional {
		lengthFinBitAndConvolutional += 2
	}
	if f.DataLength >= 1 << 14 {
		return FECFrameTooHighDataLength
	}
	lengthFinBitAndConvolutional |= uint16(f.DataLength) << 2
	utils.BigEndian.WriteUint16(b, lengthFinBitAndConvolutional)

	if !f.Convolutional {
		if !version.UsesIETFFrameFormat() {
			if f.FECGroup >= 1 << 48 {
				return FECFrameTooHighFECGroup
			}
			utils.BigEndian.WriteUint48(b, uint64(f.FECGroup))
		} else {
			utils.WriteVarInt(b, uint64(f.FECGroup))
		}
	} else {
		utils.WriteVarInt(b, uint64(f.FECPayloadID))
	}

	b.WriteByte(uint8(f.Offset))
	b.WriteByte(f.RepairSymbolNumber)
	if f.Offset == 0 {
		b.WriteByte(f.NumberOfPackets)
		b.WriteByte(f.NumberOfRepairSymbols)
	}
	b.Write(f.Data[:f.DataLength])
	return nil
}



func NewFrame(fecGroup protocol.FECGroup, finBit bool, offset protocol.FecFrameOffset,
							numberOfPackets byte, numberOfRepairSymbols byte, data []byte) (*FECFrame, error) {
	if fecGroup >= 1 << 48 {
		return nil, FECFrameTooHighFECGroup
	}
	if len(data) >= 1 << 15 {
		return nil, FECFrameTooMuchData
	}
	return &FECFrame{
		FECGroup:   fecGroup,
		FinBit:     finBit,
		Offset:     offset,
		DataLength: protocol.FecFrameLength(len(data)),
		NumberOfPackets: numberOfPackets,
		NumberOfRepairSymbols: numberOfRepairSymbols,
		Data:       data,
	}, nil
}

// DataLen gives the length of data in bytes
func (f *FECFrame) DataLen() protocol.FecFrameLength {
	return protocol.FecFrameLength(len(f.Data))
}


func (f *FECFrame) Length(version protocol.VersionNumber) protocol.ByteCount {
	if !version.UsesIETFFrameFormat() {
		return f.lengthLegacy(version)
	}
	// TypeByte + FECGroup + FINBit & DataLength + Offset + RepairSymbolNumber (+ NumberOfPackets + NumberOfRepairSymbols if offset == 0 && repairsymbolnumber == 0)
	var sizeNumberOfPacketsAndRepairSymbols protocol.ByteCount = 0
	if f.Offset == 0 {
		sizeNumberOfPacketsAndRepairSymbols = 2
	}
	fecGroupLen := utils.VarIntLen(uint64(f.FECGroup))
	return 1 + fecGroupLen + 2 + 1 + 1 + sizeNumberOfPacketsAndRepairSymbols + protocol.ByteCount(f.DataLen())
}


func (f *FECFrame) lengthLegacy(version protocol.VersionNumber) protocol.ByteCount {
	// TypeByte + FECGroup + FINBit & DataLength + Offset + RepairSymbolNumber (+ NumberOfPackets + NumberOfRepairSymbols if offset == 0 && repairsymbolnumber == 0)
	var sizeNumberOfPackets byte = 0
	if f.Offset == 0 {
		sizeNumberOfPackets = 2
	}
	return protocol.ByteCount(1 + 6 + 2 + 1 + 1 + sizeNumberOfPackets) + protocol.ByteCount(f.DataLen())
}

func (f *FECFrame) GetFECPayloadID() protocol.FECPayloadID {
	if !f.Convolutional {
		panic("the frame is not convolutional: it does not have a fec payload id")
	}
	return f.FECPayloadID
}